package de.dhbw.ka.tit09.picsim.model;

import de.dhbw.ka.tit09.picsim.BitUtility;

/**
 * Model of the PIC special function registers.
 * 
 * @author Andreas Bender
 * 
 */
public class SFRegisters extends AbstractModelObject {
	private int TMR0;
	private int PCL;
	private int STATUS;
	private int FSR;
	private int PORTA;
	private int PORTB;
	private int EEDATA;
	private int EEADR;
	private int PCLATH;
	private int INTCON;
	private int OPTION;
	private int TRISA;
	private int TRISB;
	private int EECON1;

	// private int portALatch;
	// private int portBLatch;

	private int EECON2; // dummy register
	private int prescaler;

	/**
	 * Constructor.
	 */
	public SFRegisters() {
		initSFRegisters();
	}

	/**
	 * Initializes the registers.
	 */
	public void initSFRegisters() {
		prescaler = 0;

		setPCL(0);
		setSTATUS(0x18);
		setFSR(0);
		setPORTA(0);
		setPORTB(0);
		setEEDATA(0);
		setEEADR(0);
		setPCLATH(0);
		setINTCON(0);
		setOPTION(0xFF);
		setTRISA(0x1F);
		setTRISB(0xFF);
		setEECON1(0);
		setTMR0(0);
	}

	/**
	 * Returns TMR0 value.
	 * 
	 * @return the TMR0 value
	 */
	public int getTMR0() {
		return TMR0;
	}

	/**
	 * Sets TMR0.
	 * 
	 * @param pTMR0 new TMR0 value
	 */
	public void setTMR0(int pTMR0) {
		firePropertyChange("TMR0", this.TMR0, this.TMR0 = pTMR0);
	}

	/**
	 * Increments TMR0.
	 */
	public void incTMR0() {
		incPrescaler();
	}

	/**
	 * Increments timer0 with prescaler support.
	 */
	private void incTMR0AfterPrescaler() {
		if (TMR0 == 0xFF) {
			firePropertyChange("TMR0", this.TMR0, TMR0 = 0);

			// overflow, set T0IF
			setINTCON(BitUtility.getBitModifiedInt(INTCON, 2, true));
		} else {
			firePropertyChange("TMR0", this.TMR0, ++TMR0);
		}
	}

	private void incPrescaler() {
		// check if PSA is clear ==> timer0 prescaler
		if (!BitUtility.getBitValueFromInt(OPTION, 3)) {
			if (prescaler == 0) {
				// load prescaler with start value
				prescaler = (int) Math.pow(2,
						(BitUtility.getValueInRange(OPTION, 0, 2) + 1)) - 1;

				incTMR0AfterPrescaler();
			} else {
				prescaler--;
			}
		} else {
			// PSA is set ==> timer0 1:1; WDT prescaler
			incTMR0AfterPrescaler();
		}
	}

	/**
	 * Returns the PCL value.
	 * 
	 * @return the PCL value
	 */
	public int getPCL() {
		return PCL;
	}

	/**
	 * Sets the PCL register.
	 * 
	 * @param pPCL the new PCL value
	 */
	public void setPCL(int pPCL) {
		firePropertyChange("PCL", this.PCL, this.PCL = pPCL);
	}

	/**
	 * Returns STATUS register value.
	 * 
	 * @return STATUS register value
	 */
	public int getSTATUS() {
		return STATUS;
	}

	/**
	 * Sets the STATUS register.
	 * 
	 * @param pSTATUS the new STATUS register value
	 */
	public void setSTATUS(int pSTATUS) {
		firePropertyChange("STATUS", this.STATUS, this.STATUS = pSTATUS);
	}

	/**
	 * Returns FSR register value.
	 * 
	 * @return FSR register value
	 */
	public int getFSR() {
		return FSR;
	}

	/**
	 * Sets the FSR register.
	 * 
	 * @param pFSR the new FSR register value
	 */
	public void setFSR(int pFSR) {
		firePropertyChange("FSR", this.FSR, this.FSR = pFSR);
	}

	/**
	 * Returns PORTA value.
	 * 
	 * @return the PORTA value
	 */
	public int getPORTA() {
		return PORTA;
	}

	/**
	 * Sets PORTA.
	 * 
	 * @param pPORTA the new PORTA value
	 */
	public void setPORTA(int pPORTA) {
		// check TMR0 counter mode
		if (BitUtility.getBitValueFromInt(OPTION, 5)) {
			// check T0SE
			if (BitUtility.getBitValueFromInt(OPTION, 4)) {
				// check falling edge
				if (BitUtility.getBitValueFromInt(PORTA, 4)
						&& !BitUtility.getBitValueFromInt(pPORTA, 4)) {
					incTMR0();
				}
			} else {
				// check raising edge
				if (!BitUtility.getBitValueFromInt(PORTA, 4)
						&& BitUtility.getBitValueFromInt(pPORTA, 4)) {
					incTMR0();
				}
			}
		}

		firePropertyChange("PORTA", this.PORTA, this.PORTA = pPORTA);
	}

	/**
	 * Returns PORTB value.
	 * 
	 * @return the PORTB value
	 */
	public int getPORTB() {
		// // clear RBIF what is mismatch condition? FIXME ignore
		// setINTCON(BitUtility.getBitModifiedInt(INTCON, 0, false));
		return PORTB;
	}

	/**
	 * Sets PORTB.
	 * 
	 * @param pPORTB the new PORTB value
	 */
	public void setPORTB(int pPORTB) {
		// // clear RBIF ignore
		// setINTCON(BitUtility.getBitModifiedInt(INTCON, 0, false));

		// check interrupt conditions

		// check RB7:RB4 change interrupt
		if ((((PORTB & TRISB) & 0xF0) ^ ((pPORTB & TRISB) & 0xF0)) != 0) {
			// set RBIF
			setINTCON(BitUtility.getBitModifiedInt(INTCON, 0, true));
		}

		// check INTEDG bit
		if (BitUtility.getBitValueFromInt(OPTION, 6)) {
			// check raising edge
			if (!BitUtility.getBitValueFromInt(PORTB, 0)
					&& BitUtility.getBitValueFromInt(pPORTB, 0)) {
				// set RB0/INT flag
				setINTCON(BitUtility.getBitModifiedInt(INTCON, 1, true));
			}
		} else {
			// check falling edge
			if (BitUtility.getBitValueFromInt(PORTB, 0)
					&& !BitUtility.getBitValueFromInt(pPORTB, 0)) {
				// set RB0/INT flag
				setINTCON(BitUtility.getBitModifiedInt(INTCON, 1, true));
			}
		}

		firePropertyChange("PORTB", this.PORTB, this.PORTB = pPORTB);
	}

	/**
	 * Returns the EEDATA register value.
	 * 
	 * @return the EEDATA register value
	 */
	public int getEEDATA() {
		return EEDATA;
	}

	/**
	 * Sets the EEDATA register.
	 * 
	 * @param pEEDATA the new EEDATA register value
	 */
	public void setEEDATA(int pEEDATA) {
		firePropertyChange("EEDATA", this.EEDATA, this.EEDATA = pEEDATA);
	}

	/**
	 * Returns the EEADR register value.
	 * 
	 * @return the EEADR register value
	 */
	public int getEEADR() {
		return EEADR;
	}

	/**
	 * Sets the EEADR register.
	 * 
	 * @param pEEADR the new EEADR register value
	 */
	public void setEEADR(int pEEADR) {
		firePropertyChange("EEADR", this.EEADR, this.EEADR = pEEADR);
	}

	/**
	 * Returns the PCLATH register value.
	 * 
	 * @return the PCLATH register value
	 */
	public int getPCLATH() {
		return PCLATH;
	}

	/**
	 * Sets the PCLATH register.
	 * 
	 * @param pPCLATH the new PCLATH register value
	 */
	public void setPCLATH(int pPCLATH) {
		firePropertyChange("PCLATH", this.PCLATH, this.PCLATH = pPCLATH);
	}

	/**
	 * Returns the INTCON register value.
	 * 
	 * @return the INTCON register value
	 */
	public int getINTCON() {
		return INTCON;
	}

	/**
	 * Sets the INTCON register.
	 * 
	 * @param pINTCON the new INTCON register value
	 */
	public void setINTCON(int pINTCON) {
		firePropertyChange("INTCON", this.INTCON, this.INTCON = pINTCON);
	}

	/**
	 * Returns the OPTION register value.
	 * 
	 * @return the OPTION register value
	 */
	public int getOPTION() {
		return OPTION;
	}

	/**
	 * Sets the OPTION register.
	 * 
	 * @param pOPTION the new OPTION register value
	 */
	public void setOPTION(int pOPTION) {
		// // test prescaler status change
		// if ((OPTION & 7) != (pOPTION & 7))

		firePropertyChange("OPTION", this.OPTION, this.OPTION = pOPTION);
	}

	/**
	 * Returns the TRISA register value.
	 * 
	 * @return the TRISA register value
	 */
	public int getTRISA() {
		return TRISA;
	}

	/**
	 * Sets the TRISA register.
	 * 
	 * @param pTRISA the new TRISA register value
	 */
	public void setTRISA(int pTRISA) {
		firePropertyChange("TRISA", this.TRISA, this.TRISA = pTRISA);
	}

	/**
	 * Returns the TRISB register value.
	 * 
	 * @return the TRISB register value
	 */
	public int getTRISB() {
		return TRISB;
	}

	/**
	 * Sets the TRISB register.
	 * 
	 * @param pTRISB the new TRISB register value
	 */
	public void setTRISB(int pTRISB) {
		firePropertyChange("TRISB", this.TRISB, this.TRISB = pTRISB);
	}

	/**
	 * Returns the EECON1 register value.
	 * 
	 * @return the EECON1 register value
	 */
	public int getEECON1() {
		return EECON1;
	}

	/**
	 * Sets the EECON1 register.
	 * 
	 * @param pEECON1 the new EECON1 register value
	 */
	public void setEECON1(int pEECON1) {
		if (BitUtility.getBitValueFromInt(pEECON1, 1)) {
			// WR is set, check write conditions
			if (BitUtility.getBitValueFromInt(EECON1, 2)) {
				// WREN is set, check EECON2 value
				PICModelProvider picModel = PICModelProvider.getInstance();
				if (EECON2 - (picModel.getProgramCounter() - 1) == 0x2000) {
					// start write cycle
					picModel.getEEPROMRegisters().setRegisterAtAddress(EEADR,
							EEDATA);

					// set EEIF
					firePropertyChange("EECON1", this.EECON1,
							this.EECON1 = 0x14);
				}
			}
			// reset write state in EECON2
			EECON2 = 0;
		} else if (BitUtility.getBitValueFromInt(pEECON1, 0)) {
			// RD is set
			EEDATA = (Integer) PICModelProvider.getInstance()
					.getEEPROMRegisters().getRegisterAtAddress(EEADR);
		} else {
			firePropertyChange("EECON1", this.EECON1, this.EECON1 = pEECON1);
		}
	}

	/**
	 * Sets the EECON2 register.
	 * 
	 * @param pEECON2 the new EECON2 register value
	 */
	public void setEECON2(int pEECON2) {
		// firePropertyChange("EECON1", this.EECON1, this.EECON1 = pEECON1);
		int pc = PICModelProvider.getInstance().getProgramCounter();
		// this.EECON2 = pEECON2;

		if (pEECON2 == 0x55 && EECON2 == 0) {
			EECON2 = 0x1000 + pc;
		} else if (pEECON2 == 0xAA && EECON2 == 0x1000 + pc - 2) {
			EECON2 = 0x2000 + pc;
		} else {
			EECON2 = 0;
		}
	}

	/**
	 * Returns the RP0 bit value.
	 * 
	 * @return the RP0 value
	 */
	public boolean getRP0() {
		return BitUtility.getBitValueFromInt(STATUS, 5);
	}

	/**
	 * Returns the C bit value.
	 * 
	 * @return the C value
	 */
	public boolean getC() {
		return BitUtility.getBitValueFromInt(STATUS, 0);
	}

	/**
	 * Sets the C bit.
	 * 
	 * @param c the new C bit value
	 */
	public void setC(boolean c) {
		setSTATUS(BitUtility.getBitModifiedInt(STATUS, 0, c));
	}

	/**
	 * Returns the Z bit value.
	 * 
	 * @return the Z value
	 */
	public boolean getZ() {
		return BitUtility.getBitValueFromInt(STATUS, 2);
	}

	/**
	 * Sets the Z bit.
	 * 
	 * @param z the new Z bit value
	 */
	public void setZ(boolean z) {
		setSTATUS(BitUtility.getBitModifiedInt(STATUS, 2, z));
	}

	/**
	 * Returns the DC bit value.
	 * 
	 * @return the DC value
	 */
	public boolean getDC() {
		return BitUtility.getBitValueFromInt(STATUS, 1);
	}

	/**
	 * Sets the DC bit.
	 * 
	 * @param dc the new DC bit value
	 */
	public void setDC(boolean dc) {
		setSTATUS(BitUtility.getBitModifiedInt(STATUS, 1, dc));
	}

	/**
	 * Sets the RP0 bit.
	 * 
	 * @param rp0 the new RP0 bit value
	 */
	public void setRP0(boolean rp0) {
		setSTATUS(BitUtility.getBitModifiedInt(STATUS, 5, rp0));
	}

	/**
	 * Sets the special function register at the given address.
	 * 
	 * @param address the sf register address
	 * @param value the new register value
	 */
	public void setSFRAtAddress(int address, int value) {
		switch (address) {
		case 2:
			setPCL(value);
			return;
		case 3:
			setSTATUS(value);
			return;
		case 4:
			setFSR(value);
			return;
		case 0xA:
			setPCLATH(value);
			return;
		case 0xB:
			setINTCON(value);
			return;
		}

		if (getRP0()) {
			// Bank 1
			switch (address) {
			case 1:
				setOPTION(value);
				return;
			case 5:
				setTRISA(value);
				return;
			case 6:
				setTRISB(value);
				return;
			case 8:
				setEECON1(value);
				return;
			case 9:
				setEECON2(value);
				return;
			}
		} else {
			// Bank 0
			switch (address) {
			case 1:
				setTMR0(value);
				return;
			case 5:
				setPORTA(value);
				return;
			case 6:
				setPORTB(value);
				return;
			case 8:
				setEEDATA(value);
				return;
			case 9:
				setEEADR(value);
				return;
			}
		}
	}

	/**
	 * Returns the value of the SFR at the given address.
	 * 
	 * @param address the sf register address
	 * @return the sf register value
	 */
	public int getSFRAtAddress(int address) {
		switch (address) {
		case 2:
			return getPCL();
		case 3:
			return getSTATUS();
		case 4:
			return getFSR();
		case 0xA:
			return getPCLATH();
		case 0xB:
			return getINTCON();
		}

		if (getRP0()) {
			// Bank 1
			switch (address) {
			case 1:
				return getOPTION();
			case 5:
				return getTRISA();
			case 6:
				return getTRISB();
			case 8:
				return getEECON1();
			}
		} else {
			// Bank 0
			switch (address) {
			case 1:
				return getTMR0();
			case 5:
				return getPORTA();
			case 6:
				return getPORTB();
			case 8:
				return getEEDATA();
			case 9:
				return getEEADR();
			}
		}

		return 0;
	}


	// private int setLatchAndGetPortValue(int latchValue, boolean isPortA) {
	// if (isPortA) {
	// portALatch = latchValue;
	// return (PORTA & TRISA) | (latchValue & ~TRISA);
	// } else {
	// portBLatch = latchValue;
	// return (PORTB & TRISB) | (latchValue & ~TRISB);
	// }
	// }
}
